Cesium 自定义 Primitive 实现三角形绘制

在 Cesium 开发中,有时我们需要自定义图形来满足特定的可视化需求。本文将介绍如何通过继承 Cesium 的 Primitive 类来实现自定义三角形绘制,并深入解析 WebGL 底层实现原理。

基本原理

Cesium 提供了丰富的可视化组件,但有些特殊场景下需要我们直接操作 WebGL 来实现定制化的图形渲染。自定义 Primitive 允许我们直接使用 WebGL 着色器程序来控制顶点数据和渲染流程。

代码实现

下面是一个完整的示例,展示如何创建自定义 Primitive 来绘制一个三角形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Cesium自定义Primitive实现三角形绘制</title>
<link rel="stylesheet" href="./Cesium-1.107.1/Cesium//Widgets/widgets.css" />
<script src="./Cesium-1.107.1//Cesium/Cesium.js"></script>
<style>
html,
body {
width: 100%;
height: 100%;
}

#cesiumContainer {
width: 100%;
height: 100%;
}
</style>
</head>

<body>
<div id="cesiumContainer"></div>
<script>
Cesium.Ion.defaultAccessToken =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJlMTk4ZTYyNy00MjkxLTRmZWYtOTg1MS0wOThjM2YzMzIzYzEiLCJpZCI6NzEyMSwic2NvcGVzIjpbImFzciIsImdjIl0sImlhdCI6MTU0ODMxNzI5OX0.rKV8Ldl_bgR3lVvNsbHhTX62j8JH8ADCIWAwk7tXpr8";

class CustomPrimitive extends Cesium.Primitive {
constructor(options) {
super(options);
const modelCenter = Cesium.Cartesian3.fromDegrees(121.474509, 31.233368, 0);
const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(modelCenter);
this._modelMatrix = modelMatrix;
}

createCommand = (frameState, modelMatrix) => {
const context = frameState.context;
const positionBuffer = Cesium.Buffer.createVertexBuffer({
context: context,
typedArray: new Float32Array([
100000, 500000, 50000, -200000, -100000, 50000, 500000, -300000, 50000,
]),
usage: Cesium.BufferUsage.STATIC_DRAW, // STATIC_DRAW则表示此缓冲数据平常不咋动
});
const attributes = [
{
index: 0,
enabled: true,
vertexBuffer: positionBuffer,
componentsPerAttribute: 3,
componentDatatype: Cesium.ComponentDatatype.FLOAT,
normalize: false,
offsetInBytes: 0,
strideInBytes: 0, // tightly packed
instanceDivisor: 0, // not instanced
},
];
const vertexArray = new Cesium.VertexArray({
context: context,
attributes: attributes,
});

const vertexShaderSource = `
in vec3 position;
void main() {
gl_Position = czm_projection * czm_view * czm_model * vec4(position, 1.0);
}
`;
const fragmentShaderSource = `
#version 300 es
precision highp float;
out vec4 fragColor;

void main() {
fragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
`;
const attributeLocations = {
position: 0,
};

const shaderProgram = Cesium.ShaderProgram.fromCache({
context: context,
vertexShaderSource: vertexShaderSource,
fragmentShaderSource: fragmentShaderSource,
attributeLocations: attributeLocations,
});

const renderState = Cesium.RenderState.fromCache({
depthTest: {
enabled: true,
},
});

return new Cesium.DrawCommand({
modelMatrix: modelMatrix,
vertexArray: vertexArray,
shaderProgram: shaderProgram,
renderState: renderState,
pass: Cesium.Pass.OPAQUE, // Cesium 中用于渲染顺序的一个参数
});
};

update = frameState => {
const command = this.createCommand(frameState, this._modelMatrix);
frameState.commandList.push(command);
};
}

const viewer = new Cesium.Viewer("cesiumContainer");
const customPrimitive = new CustomPrimitive();
viewer.scene.primitives.add(customPrimitive);
</script>
</body>
</html>

代码解析

1. 自定义 Primitive 类

我们通过继承Cesium.Primitive类创建自定义图元:

1
2
3
4
5
6
7
8
9
10
class CustomPrimitive extends Cesium.Primitive {
constructor(options) {
super(options);
const modelCenter = Cesium.Cartesian3.fromDegrees(121.474509, 31.233368, 0);
const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(modelCenter);
this._modelMatrix = modelMatrix;
}

// ...其他方法
}

在构造函数中,我们设置了图元的位置(上海的经纬度坐标)并创建了模型矩阵。

2. 创建顶点缓冲区

1
2
3
4
5
6
7
const positionBuffer = Cesium.Buffer.createVertexBuffer({
context: context,
typedArray: new Float32Array([
100000, 500000, 50000, -200000, -100000, 50000, 500000, -300000, 50000,
]),
usage: Cesium.BufferUsage.STATIC_DRAW,
});

这里我们定义了三角形的三个顶点坐标,每个顶点包含 X、Y、Z 三个值。使用STATIC_DRAW表示这些数据不会频繁更改。

3. 设置顶点属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const attributes = [
{
index: 0,
enabled: true,
vertexBuffer: positionBuffer,
componentsPerAttribute: 3,
componentDatatype: Cesium.ComponentDatatype.FLOAT,
normalize: false,
offsetInBytes: 0,
strideInBytes: 0,
instanceDivisor: 0,
},
];

const vertexArray = new Cesium.VertexArray({
context: context,
attributes: attributes,
});

这部分代码配置了顶点属性,指定了顶点数据的格式和布局。

4. 着色器程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const vertexShaderSource = `
in vec3 position;
void main() {
gl_Position = czm_projection * czm_view * czm_model * vec4(position, 1.0);
}
`;

const fragmentShaderSource = `
#version 300 es
precision highp float;
out vec4 fragColor;

void main() {
fragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
`;

着色器程序包含:

  • 顶点着色器:处理每个顶点的位置变换
  • 片段着色器:设置三角形的颜色(这里是黄色:RGB(1.0, 1.0, 0.0))

5. 创建绘制命令

1
2
3
4
5
6
7
return new Cesium.DrawCommand({
modelMatrix: modelMatrix,
vertexArray: vertexArray,
shaderProgram: shaderProgram,
renderState: renderState,
pass: Cesium.Pass.OPAQUE,
});

绘制命令将所有元素组合起来,告诉 Cesium 引擎如何渲染我们的三角形。

6. 更新函数

1
2
3
4
update = frameState => {
const command = this.createCommand(frameState, this._modelMatrix);
frameState.commandList.push(command);
};

每一帧都会调用update方法,它创建绘制命令并将其添加到 Cesium 的命令列表中。

关键概念解释

  1. Primitive(图元):Cesium 中的基本渲染单元,代表一组具有相同渲染状态的几何图形。

  2. VertexArray(顶点数组):存储顶点数据和属性配置的对象。

  3. ShaderProgram(着色器程序):包含顶点着色器和片段着色器的程序,用于处理顶点和像素的渲染。

  4. DrawCommand(绘制命令):告诉 Cesium 如何渲染一个图元的指令。

  5. modelMatrix(模型矩阵):用于将局部坐标转换为世界坐标的矩阵。

如何扩展

这个示例只是绘制了一个简单的三角形,但您可以通过以下方式扩展它:

  1. 添加颜色变化:修改片段着色器为每个顶点设置不同的颜色,并在顶点之间进行插值。

  2. 添加纹理:为三角形添加纹理映射。

  3. 实现动画效果:通过在每一帧修改顶点位置实现动画。

  4. 添加交互功能:实现点击选中、拖拽等交互功能。

总结

通过自定义 Primitive,我们可以在 Cesium 中绘制任何想要的几何图形,充分发挥 WebGL 的强大功能。虽然 Cesium 提供了许多高级组件,但了解底层原理可以帮助我们实现更加定制化的可视化效果。


作者

wangyawei

发布于

2025-03-20

更新于

2025-03-21

许可协议